Ziva Vatra - home :: Odds and Sods :: Git Hints

Git hints, tips and troubleshooting

Here I document all the things I discovered about git and its behaviour while using it over the years. It includes tips, hints, troubleshooting and automation scripts I have written to make my life easier and felt like sharing. I felt this page was needed to remind myself of things I did before so I don't have to re-invent the wheel and to assist others who may have the same (or similar) goals.

Auto-modifying committed files with git hooks

For some context, this website runs Blosxom and each of these articles is a html file. At the bottom of each you will notice a "Page Created" and "Page last modified" footer. This is auto-created by a Perl script which is currently run manually on each file I want to change.

I wanted to automate this in a git hook, so that every time I did a git commit it would auto-update the "last modified" timestamp for each modified file. First thing I did was search online and surprisingly I didn't find anyone who had managed. The web was full of people who tried to put this in pre-commit hooks and had problems. From getting errors like "Error: Cannot Lock Ref" to having the auto modified files not part of the commit (forcing a manual commit after the auto changes) the general web consensus was that "it could not be done".

I gave it a go myself, and yes I found you could not do it with a pre-commit hook. The problem is that git uses lockfiles to make sure only one git process is running at one time on a repo. When you do a "git commit" the process launches the git pre-commit hook and the process will wait until the hook finishes. If during this time, your script tries to change the files and commit the changes, it will fail with the "Error: Cannot Lock Ref", because the git commit process cannot launch while the original process is still running. If you don't try to commit your changes the git commit will complete without error, but your auto-changes would remain in your repo rather than in the last commit.

My solution to this was to make use of a post-commit hook instead. This results in doing two commits instead of one, but it works for modifying the files in the way I wanted. In order to do this, you need two things from the script:

  1. You need to know the message from the last commit. As our post-commit hook needs itself to do a commit, we need some way of checking if we ran last time, otherwise you end up in an infinite commit loop. We do this by checking the last message and seeing if a specific string is in there.
  2. You need to know what files were modified in the last commit, in order to run the specific updates only to them.

The core part of the Perl script is shown below. I've omitted the subroutines that do the actual changes as that is specific to my use case, but it does show the logic involved in making automatic changes to the last modified files. At this point in time it only makes changes to article files, but in theory nothing stops you having subroutines to handle any file what so ever that has changed.


# First thing we check is if the last commit message was done by this hook.
# If so we do nothing more to prevent loops
$lastlog = `git log -1`;
exit(0) if ($lastlog =~ m/post-commit-hook:/g);

# The command below tells us the file name and what was done.
# If a file was modified it starts with "M", if the file was renamed
# we get "R", etc..

foreach(`git diff --name-status --cached HEAD^`) {
	# First check if the file was modified, we only want to update modified
	# files, not files that have been renamed/moved/other.
	next unless (m~M\W+~);

	# If we are dealing with modifications, we extract the filename
	($_) = $_ =~ m/M\W+(.*)/;

	chomp;
	# update CMS article files.
	updateTimestamp("$_") if (m~/article$~);
	updateVimConfig("$_") if (m~/article$~);
}

# As we made changes, we have to commit again, with auto-message for reference
system("git commit -m 'post-commit-hook: Updating CMS timestamps and vim config' ./");
exit(0);

With this system you do end up getting two commits for every single human commit, half of which are identical auto-commits, but the system works as shown on the pages of this website :-)

Tracking git hooks within the repo

If you want a hook to be part of your repo (so that changes can be tracked) on modern git versions you can create your own custom path, "git add" it, then set your local git config to point to it for the hooks as shown:

git config --local core.hooksPath  ./.githooks/

This config change does not persist across cloned repos, so every time a new clone is created the config needs to be set for the hooks to work.

Page created: Sat Dec 30 18:34:00 2023 ][ Page last modified: Sun Dec 31 01:27:24 2023 ]